/**
 * Copyright (C) 2012 The SkyTvOS Project
 * <p/>
 * Version     Date           Author
 * ─────────────────────────────────────
 * 2014年11月4日         Root.Lu
 */

package com.coocaa.x.service.litedownloader.downloader;

import android.util.Log;

import com.coocaa.x.framework.utils.Cache;
import com.coocaa.x.framework.utils.CacheTool;
import com.coocaa.x.framework.utils.LogUtils;
import com.coocaa.x.framework.utils.Storage;
import com.coocaa.x.service.litedownloader.DownloaderService;
import com.coocaa.x.service.litedownloader.data.DownloadError;
import com.coocaa.x.service.litedownloader.data.DownloadTask;
import com.coocaa.x.service.litedownloader.data.ProcessInfo;
import com.coocaa.x.service.litedownloader.data.TaskInfo.TASK_STATE;
import com.coocaa.x.service.litedownloader.db.DownloaderDB;
import com.coocaa.x.service.litedownloader.db.DownloaderTable;
import com.coocaa.x.service.litedownloader.downloader.Downloader.DownloaderListener;
import com.skyworth.framework.skysdk.properties.SkyGeneralProperties;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DownloaderManager implements DownloaderListener, Storage.IStorageListener {
    private static DownloaderManager instance = new DownloaderManager();

    public static DownloaderManager getInstance() {
        return instance;
    }

    private static final int TASK_SIZE = 3;
    private ThreadPoolExecutor executor = null;
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();

    private DownloaderListener listener = null;

    private DownloaderTable dtable = null;
    private Cache<String, Downloader> downloadermap = new Cache<String, Downloader>();
    private CacheTool<String, DownloadTask> taskcache = null;

    private DownloaderManager() {
        int task_size = TASK_SIZE;

        Storage.addListener(this);
        if (SkyGeneralProperties.getIntConfig("x_downloader_tasksize") > 0)
            task_size = SkyGeneralProperties.getIntConfig("x_downloader_tasksize");
        executor = new ThreadPoolExecutor(task_size, task_size, 999, TimeUnit.DAYS, queue,
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        // TODO Auto-generated method stub
                        return new Thread(r, "DownloadThread" + System.currentTimeMillis());
                    }
                });
        executor.prestartAllCoreThreads();
        List<DownloadTask> waitList = new ArrayList<DownloadTask>();
        dtable = (DownloaderTable) DownloaderDB.getInstance().getTable(DownloaderTable.class);
        taskcache = new CacheTool<String, DownloadTask>(dtable);
        taskcache.flush();
        List<DownloadTask> list = taskcache.values();

        for (DownloadTask task : list) {

            if(task.savepath != null && !task.equals("")) {
                File file = new File(task.savepath);
                if (!file.exists()) {
                    LogUtils.i("save path invalid remove task: "+task.savepath);
                    taskcache.remove(task.ID);
                    if (listener != null)
                        listener.onDelete(task);
                }
            }

            switch (task.info.state) {
                case PROCESSING: {
                    doStart(task);
                    break;
                }
                case READY:
                case PREPARED: {
                    waitList.add(task);
                    break;
                }
                case STOPED:
                case FINISH:
                default: {
                    break;
                }
            }
        }
        for (DownloadTask task : waitList)
            doStart(task);
    }

    private void doStart(DownloadTask task) {
        Downloader downloader = getDownloader(task);
        LogUtils.d("start downloader:" + downloader + "  task:" + task.ID);
        task.info.state = TASK_STATE.READY;
        taskcache.update(task.ID, task);
        downloader.ready();
        executor.execute(downloader);
    }

    public void setListener(DownloaderListener listener) {
        this.listener = listener;
    }

    private boolean isAllDownloadEnd() {
        List<DownloadTask> tasks = taskcache.values();
        for (DownloadTask task : tasks) {
            switch (task.info.state) {
                case READY:
                case PREPARED:
                case PROCESSING:
                case CREATE:
                case STOPED:
                    return false;
                case FINISH:
                    break;
            }
        }
        return true;
    }

    public int add(DownloadTask task) {
        if (taskcache.size() == 0 || isAllDownloadEnd()) {
            Storage.resetReservedMemory();
        }

        int ret = DownloadTask.TASK_CODE_OK;
        synchronized (this) {
            if (taskcache.containsKey(task.ID))
                ret = DownloadTask.TASK_CODE_ALREADY_EXIST;
            else
                taskcache.add(task.ID, task);
        }
        return ret;
    }

    public int start(DownloadTask task) {
        task = refresh(task);
        int ret = DownloadTask.TASK_CODE_OK;
        synchronized (this) {
            if (taskcache.containsKey(task.ID)) {
                switch (task.info.state) {
                    case READY:
                    case PREPARED:
                    case PROCESSING: {
                        ret = DownloadTask.TASK_CODE_ALREADY_START;
                        break;
                    }
                    case CREATE:
                    case STOPED: {
                        Downloader downloader = getDownloader(task);
                        LogUtils.d("start downloader:" + downloader + "  task:" + task.ID);
                        task.info.state = TASK_STATE.READY;
                        taskcache.update(task.ID, task);
                        downloader.ready();
                        executor.execute(downloader);
                        break;
                    }
                    case FINISH: {
                        ret = DownloadTask.TASK_CODE_ALREADY_FINISH;
                        break;
                    }
                    default: {
                        break;
                    }
                }
            } else
                ret = DownloadTask.TASK_CODE_NOT_EXIST;
        }
        return ret;
    }

    public int stop(DownloadTask task) {
        task = refresh(task);
        int ret = DownloadTask.TASK_CODE_OK;
        synchronized (this) {
            if (taskcache.containsKey(task.ID)) {
                if (!downloadermap.containsKey(task.ID))
                    ret = DownloadTask.TASK_CODE_NOT_START;
                else {
                    Downloader downloader = downloadermap.get(task.ID);
                    ret = downloader.stop();
                }
            } else
                ret = DownloadTask.TASK_CODE_NOT_EXIST;
        }
        return ret;
    }

    public int delete(DownloadTask task) {
        task = refresh(task);
        int ret = DownloadTask.TASK_CODE_OK;
        synchronized (this) {
            if (taskcache.containsKey(task.ID)) {
                Downloader downloader = downloadermap.get(task.ID);
                if (downloader != null)
                    ret = downloader.delete();
                else {
                    if (taskcache.containsKey(task.ID)) {
                        Downloader tmpDownloader = Downloader.create(task);
                        tmpDownloader.delete();
                    }
                    onDelete(task);
                }

            } else
                ret = DownloadTask.TASK_CODE_NOT_EXIST;
        }
        return ret;
    }

    public DownloadTask refresh(DownloadTask task) {
        DownloadTask ret = taskcache.get(task.ID);
        if (ret == null)
            return task;
        return ret;
    }

    public int toFirst(DownloadTask task) {
        task = refresh(task);
        Downloader tostop = null;
        int ret = DownloadTask.TASK_CODE_OK;
        synchronized (this) {
            List<Downloader> dlist = new ArrayList<Downloader>();
            for (Runnable d : queue)
                dlist.add((Downloader) d);
            queue.clear();
            Collections.sort(dlist, new Comparator<Downloader>() {
                public int compare(Downloader arg0, Downloader arg1) {
                    return arg0.createtime > arg1.createtime ? -1 : 1;
                }
            });

            Downloader d = downloadermap.get(task.ID);
            dlist.remove(d);
            dlist.add(0, d);

            for (Downloader dl : dlist) {
                try {
                    queue.put(dl);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

            List<DownloadTask> tasks = taskcache.values();
            for (DownloadTask _task : tasks) {
                if (_task.info.state == TASK_STATE.PROCESSING) {
                    if (tostop == null)
                        tostop = downloadermap.get(_task.ID);
                    else {
                        Downloader dd = downloadermap.get(_task.ID);
                        if (dd != null) {
                            if (dd.starttime < tostop.starttime)
                                tostop = dd;
                        }
                    }
                }
            }
        }
        if (tostop != null) {
            DownloadTask _task = tostop.getDownloadTask();
            if (task != null) {
                LogUtils.i(DownloaderService.TAG, "tofirst to stop task:" + _task.ID + "/" + _task.title);
                tostop.stop();
                while (_task.info.state != TASK_STATE.STOPED)
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                start(_task);
            }
        }
        return ret;
    }

    public synchronized int startAll() {
        int ret = DownloadTask.TASK_CODE_OK;
        synchronized (this) {
            List<DownloadTask> tasks = taskcache.values();
            for (DownloadTask task : tasks) {
                switch (task.info.state) {
                    case CREATE:
                    case STOPED:
                        start(task);
                        break;
                    default:
                        break;
                }
            }
        }
        return ret;
    }

    public synchronized int stopAll() {
        int ret = DownloadTask.TASK_CODE_OK;
        synchronized (this) {
            queue.clear();
            List<DownloadTask> tasks = taskcache.values();
            for (DownloadTask task : tasks) {
                switch (task.info.state) {
                    case READY:
                    case PREPARED:
                    case PROCESSING:
                        stop(task);
                    default:
                        break;
                }
            }
        }
        return ret;
    }

    public List<DownloadTask> getTaskList() {
        return taskcache.values();
    }

    @Override
    public void onReady(DownloadTask task) {
        // TODO Auto-generated method stub
        synchronized (this) {
            if (listener != null) {
                task.info.state = TASK_STATE.READY;
                taskcache.update(task.ID, task);
                listener.onReady(task);
            }
        }
    }

    @Override
    public synchronized void onPrepare(DownloadTask task) {
        // TODO Auto-generated method stub
        synchronized (this) {
            task.info.state = TASK_STATE.PREPARED;
            taskcache.update(task.ID, task);
            if (listener != null)
                listener.onPrepare(task);
        }
    }

    @Override
    public synchronized void onStart(DownloadTask task) {
        // TODO Auto-generated method stub
        synchronized (this) {
            task.info.state = TASK_STATE.PROCESSING;
            taskcache.update(task.ID, task);
            if (listener != null)
                listener.onStart(task);
        }
    }

    @Override
    public synchronized void onStop(DownloadTask task) {
        // TODO Auto-generated method stub
        synchronized (this) {
            task.info.state = TASK_STATE.STOPED;
            taskcache.update(task.ID, task);

            LogUtils.d("$$$$$$$$$$$$$$$$$onstop task.info.state:" + task.info.state);
            if (taskcache.get(task.ID) != null) {
                LogUtils.d("$$$$$$$$$$$$$$$$$onstop cache:" + taskcache.get(task.ID).info.state);
            } else {
                LogUtils.d("$$$$$$$$$$$$$$$$$onstop not cache " + task.ID);
            }

            downloadermap.remove(task.ID);

            List<DownloadTask> ret = new ArrayList<DownloadTask>();
            List<DownloadTask> tasks = getTaskList();
            for (DownloadTask t : tasks) {
                switch (t.info.state) {
                    case STOPED: {
                        ret.add(t);
                        break;
                    }
                    default:
                        break;
                }
            }
            LogUtils.d("$$$$$$$$$$$$$$$$$onstop stopped task:" + ret.size() + "/"
                    + tasks.size());
            if (listener != null)
                listener.onStop(task);
        }
    }

    @Override
    public synchronized void onFinish(DownloadTask task) {
        // TODO Auto-generated method stub
        synchronized (this) {
            task.info.state = TASK_STATE.FINISH;
            taskcache.update(task.ID, task);
            // downloadermap.remove(task.ID);
            if (listener != null)
                listener.onFinish(task);
        }
    }

    @Override
    public synchronized void onDelete(DownloadTask task) {
        // TODO Auto-generated method stub
        synchronized (this) {
            taskcache.remove(task.ID);
            downloadermap.remove(task.ID);
            if (listener != null)
                listener.onDelete(task);
        }
    }

    @Override
    public synchronized void onProcessInfo(DownloadTask task, ProcessInfo info) {
        // TODO Auto-generated method stub
        synchronized (this) {
            task.info.current = info.current;
            task.info.length = info.length;
            taskcache.update(task.ID, task);
            if (listener != null)
                listener.onProcessInfo(task, info);
        }
    }

    @Override
    public synchronized void onError(DownloadTask task, DownloadError error) {
        // TODO Auto-generated method stub
        synchronized (this) {
            if (listener != null)
                listener.onError(task, error);
        }
    }

    private synchronized Downloader getDownloader(DownloadTask task) {
        if (!downloadermap.containsKey(task.ID)) {
            Downloader downloader = Downloader.create(task);
            downloader.setListener(this);
            downloadermap.add(task.ID, downloader);
            return downloader;
        }
        return downloadermap.get(task.ID);
    }

    public boolean hasTask(String id) {
        // TODO Auto-generated method stub
        return taskcache.containsKey(id);
    }

    public DownloadTask getTask(String id) {
        // TODO Auto-generated method stub
        return taskcache.get(id);
    }

    @Override
    public void onStorageMounted(Storage.SDevice device) {

    }

    @Override
    public void onStorageRemoved(Storage.SDevice device) {

        synchronized (this) {

            List<DownloadTask> list = taskcache.values();
            for (DownloadTask task : list) {
                if(task.savepath == null || task.savepath.equals(""))
                    continue;

                File file = new File(task.savepath);
                if (!file.exists()) {
                    LogUtils.i("onStorageRemoved remove task: "+task.savepath);
                    taskcache.remove(task.ID);
                    downloadermap.remove(task.ID);
                    if (listener != null)
                        listener.onDelete(task);
                }
            }
        }

    }
}
